<?php

declare (strict_types=1);

namespace app\controller\admin;

use org\Data;
use think\facade\Db;
use think\facade\View;
use think\facade\Config;
use think\facade\Request;

class Database extends Common
{
    public function backup($act = null)
    {
        if ($act == 'export') {
            $tables = input('post.');
            $id = input('get.id/d');
            $start = input('get.start/d');
            if (!empty($tables)) {
                $tables = $tables['tables'];
            }

            //初始化
            if (!empty($tables)) {
                $config = [
                    'hostname' => env('DB_HOST'),
                    'hostport' => env('DB_PORT'),
                    'database' => env('DB_NAME'),
                    'path'     => config('cy.db_path'),  //路径
                    'part'     => config('cy.db_part'),  //分卷大小 20M
                    'compress' => config('cy.db_compress'),  //0:不压缩 1:启用压缩
                    'level'    => config('cy.db_level'),  //压缩级别, 1:普通 4:一般  9:最高
                ];

                if (!is_dir($config['path'])) {
                    mkdir($config['path'], 0777, true);
                }
                if (!is_writeable($config['path'])) {
                    return error(lang('请检查以下目录是否具有可写权限：'.$config['path']));
                }
                //检查是否有正在执行的任务
                $lock = $config['path']. DIRECTORY_SEPARATOR .'backup.lock';
                if (is_file($lock) && time() - file_get_contents($lock) < 300) {
                    return error(lang('检测到有一个备份任务正在执行，请稍后再试'));
                } else {
                    //创建锁文件
                    file_put_contents($lock, time());
                }

                session('backup_config', $config);

                //生成备份文件信息
                $file = array(
                    'name' => date('Ymd-His', time()),
                    'part' => 1,
                );
                session('backup_file', $file);

                //缓存要备份的表
                session('backup_tables', $tables);

                //创建备份文件
                $database = new Data($file, $config);
                if (false !== $database->create()) {
                    $tab = array('id' => 0, 'start' => 0);
                    return success(lang('初始化成功'), '', array('tables' => $tables, 'tab' => $tab));
                } else {
                    return error(lang('初始化失败，备份文件创建失败'));
                }
            } elseif (is_numeric($id) && is_numeric($start)) {
                $tables = session('backup_tables');
                $database = new Data(session('backup_file'), session('backup_config'));
                $start  = $database->backup($tables[$id], $start);
                if (false === $start) {
                    return error(lang('系统错误'));
                } elseif (0 === $start) {//下一表
                    if (isset($tables[++$id])) {
                        $tab = array('id' => $id, 'start' => 0);
                        return success(lang('备份完成'), '', ['tab' => $tab]);
                    } else { //备份完成，清空缓存
                        unlink(session('backup_config.path') . DIRECTORY_SEPARATOR . 'backup.lock');
                        session('backup_tables', null);
                        session('backup_file', null);
                        session('backup_config', null);
                        add_log('备份完成', $this->request->user->username);
                        return success(lang('备份完成'));
                    }
                } else {
                    $tab  = array('id' => $id, 'start' => $start[0]);
                    $rate = floor(100 * ($start[0] / $start[1]));
                    return success(lang('正在备份...')."({$rate}%)", '', array('tab' => $tab));
                }
            } else { //出错
                return error(lang('参数错误'));
            }
        }
        $list  = Db::query('SHOW TABLE STATUS');
        $list  = array_map('array_change_key_case', $list);
        View::assign('list', $list);
        return View::fetch();
    }

    public function optimize()
    {
        $tables = Request::request('tables');
        if ($tables) {
            if (is_array($tables)) {
                $tables = implode('`,`', $tables);
                $r = Db::execute("OPTIMIZE TABLE `{$tables}`");
                if ($r) {
                    add_log("优化数据表", $this->request->user->username);
                    return success(lang('数据表优化完成'), (string) url('admin/database/backup'));
                } else {
                    return error(lang('数据表优化出错，请重试'));
                }
            } else {
                $r = Db::execute("OPTIMIZE TABLE `{$tables}`");
                if ($r) {
                    add_log("优化数据表：{$tables}", $this->request->user->username);
                    return success(lang('数据表优化完成').$tables, (string) url('admin/database/backup'));
                } else {
                    return error(lang('数据表优化出错：').$tables);
                }
            }
        } else {
            return error(lang('请指定要优化的表'));
        }
    }

    public function repair()
    {
        $tables = Request::request('tables');
        if ($tables) {
            if (is_array($tables)) {
                $tables = implode('`,`', $tables);
                $r = Db::execute("REPAIR TABLE `{$tables}`");
                if ($r) {
                    add_log("修复数据表", $this->request->user->username);
                    return success(lang('数据表修复完成'));
                } else {
                    return error(lang('数据表修复出错请重试！'));
                }
            } else {
                $r = Db::execute("REPAIR TABLE `{$tables}`");
                if ($r) {
                    add_log("修复数据表：{$tables}", $this->request->user->username);
                    return success(lang('数据表修复完成').$tables, (string) url('admin/database/backup'));
                } else {
                    return error(lang('数据表修复出错：').$tables);
                }
            }
        } else {
            return error(lang('请指定要修复的表'));
        }
    }

    public function recovery(string $act = '', ?int $time = null, ?int $part = null, ?int $start = null)
    {

        //读取备份配置
        $config = [
            'hostname' => env('DB_HOST'),
            'hostport' => env('DB_PORT'),
            'database' => env('DB_NAME'),
            'path'     => config('cy.db_path'),  //路径
            'part'     => config('cy.db_part'),  //分卷大小 20M
            'compress' => config('cy.db_compress'),  //0:不压缩 1:启用压缩
            'level'    => config('cy.db_level'),  //压缩级别, 1:普通 4:一般  9:最高
        ];

        //判断目录是否存在
        if (!is_dir($config['path'])) {
            mkdir($config['path'], 0777, true);
        }
        if (!is_writeable($config['path'])) {
            return error(lang('请检查以下目录是否具有可写权限：'.$config['path']));
        }

        if ($act == 'del') {
            if ($time) {
                $name  = date('Ymd-His', $time) . '-*.sql*';
                $path  = $config['path']. DIRECTORY_SEPARATOR . $name;
                array_map("unlink", glob($path));
                if (count(glob($path))) {
                    return error(lang('备份文件删除失败，请检查权限'));
                } else {
                    add_log('删除数据备份：'.$path, $this->request->user->username);
                    return success(lang('操作成功'), (string) url('admin/database/recovery'));
                }
            } else {
                return error(lang('参数错误'));
            }
        }

        if ($act == 'import') {
            if (is_numeric($time) && is_null($part) && is_null($start)) { //初始化
                //获取备份文件信息
                $name  = date('Ymd-His', $time) . '-*.sql*';
                $path  = $config['path']. DIRECTORY_SEPARATOR . $name;
                $files = glob($path);
                $list  = array();
                foreach ($files as $name) {
                    $basename = basename($name);
                    $match    = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d');
                    $gz       = preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql.gz$/', $basename);
                    $list[$match[6]] = array($match[6], $name, $gz);
                }
                ksort($list);

                //检测文件正确性
                $last = end($list);
                if (count($list) === $last[0]) {
                    session('backup_list', $list); //缓存备份列表
                    return success(lang('初始化完成'), '', array('part' => 1, 'start' => 0));
                } else {
                    return error(lang('备份文件可能已经损坏，请检查'));
                }
            } elseif (is_numeric($part) && is_numeric($start)) {
                $list  = session('backup_list');
                $db = new Data($list[$part], array(
                    'path' => $config['path']. DIRECTORY_SEPARATOR,
                    'compress' => $list[$part][2]
                ));
                $start = $db->import($start);

                if (false === $start) {
                    return error(lang('还原数据出错'));
                } elseif (0 === $start) { //下一卷
                    if (isset($list[++$part])) {
                        $data = array('part' => $part, 'start' => 0);
                        return error(lang('正在还原...').$part, '', $data);
                    } else {
                        session('backup_list', null);
                        add_log('还原完成', $this->request->user->username);
                        return success(lang('还原完成'));
                    }
                } else {
                    $data = array('part' => $part, 'start' => $start[0]);
                    if ($start[1]) {
                        $rate = floor(100 * ($start[0] / $start[1]));
                        return success(lang('正在还原...')."#{$part} ({$rate}%)", '', $data);
                    } else {
                        $data['gz'] = 1;
                        return success(lang('正在还原...')."#{$part}", '', $data);
                    }
                }
            } else {
                return error(lang('参数错误'));
            }
        }


        //列出备份文件列表
        $flag = \FilesystemIterator::KEY_AS_FILENAME;
        $glob = new \FilesystemIterator($config['path'], $flag);

        $list = array();
        foreach ($glob as $name => $file) {
            if (preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql(?:\.gz)?$/', $name)) {
                $name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d');

                $date = "{$name[0]}-{$name[1]}-{$name[2]}";
                $time = "{$name[3]}:{$name[4]}:{$name[5]}";
                $part = $name[6];

                if (isset($list["{$date} {$time}"])) {
                    $info = $list["{$date} {$time}"];
                    $info['part'] = max($info['part'], $part);
                    $info['size'] = $info['size'] + $file->getSize();
                } else {
                    $info['part'] = $part;
                    $info['size'] = $file->getSize();
                }
                $extension        = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION));
                $info['compress'] = ($extension === 'SQL') ? '-' : $extension;
                $info['time']     = strtotime("{$date} {$time}");
                $list["{$date} {$time}"] = $info;
                $list_time = array_column($list, 'time');
                array_multisort($list_time, SORT_DESC, $list);
            }
        }
        View::assign('list', $list);
        return View::fetch();
    }
}
